﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Web;

namespace Tilver.Web
{
    /// <summary>
    /// URL解析类
    /// </summary>
    public class UrlParser : ICloneable, IComparable
    {
        #region Properties
        private const string _schemeRegex = @"([^:]+):";
        private const string _mailtoRegex = @"(mailto:)(([^@]+)@(.+))";
        private const string _urlRegex = @"([^:]+)://(([^:@]+)(:([^@]+))?@)?([^:/?#]+)(:([d]+))?([^?#]+)?(\?([^#]+))?(#(.*))?";

        //解析构造函数中传入的已转义的QueryString
        private Encoding _encoding { get; set; }

        /// <summary>
        /// 获取此 URI 的方案名称，如：HTTP
        /// </summary>
        public string Scheme { get; set; }

        /// <summary>
        /// 获取用户名
        /// </summary>
        public string User { get; set; }

        /// <summary>
        /// 获取密码
        /// </summary>
        public string Password { get; set; }

        /// <summary>
        /// 获取此实例的主机部分
        /// </summary>
        public string Host { get; set; }

        /// <summary>
        /// 获取此 URI 的端口号
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// 获取一个值，该值指示 URI 的端口值是否为此方案的默认值
        /// </summary>
        public bool IsDefaultPort { get; set; }

        /// <summary>
        /// 获取 URI 的路径
        /// </summary>
        public string Path { get; set; }

        /// <summary>
        /// 获取是否是相对路径
        /// </summary>
        public bool Relative { get; set; }

        /// <summary>
        /// 获取QueryString
        /// </summary>
        public NameValueCollection QueryString { get; set; }

        /// <summary>
        /// 获取转义 URI 片段
        /// </summary>
        public string Fragment { get; set; }
        #endregion

        #region Methods
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="url">URL路径</param>
        /// <param name="encoding">设置字符集用于解析已转义（与编码同义）的QueryString中的值（如果不存在已转义的值，则不用设置），默认UTF8</param>
        public UrlParser(string url, Encoding encoding = null)
        {
            _encoding = encoding ?? Encoding.UTF8;
            Reset();
            Match m = new Regex(_schemeRegex).Match(url);
            if (m.Success)
            {
                if (m.Groups[1].Captures[0].Value.ToLower().Equals("mailto"))
                    DecodeMailTo(url);
                else
                    DecodeURL(url);
            }
        }

        /// <summary>
        /// 获取已编码的URL
        /// </summary>
        /// <param name="encoding">编码使用的字符集，如果为空则使用构造函数中传入的字符集</param>
        /// <returns></returns>
        public string GetEncodeUrl(Encoding encoding = null)
        {
            return GetUrl(true, encoding);
        }

        /// <summary>
        /// 获取已解码的URL
        /// </summary>
        /// <returns></returns>
        public string GetDecodeUrl()
        {
            return GetUrl(false);
        }

        private string GetUrl(bool escaped = true, Encoding encoding = null)
        {
            if (Scheme.Equals("mailto"))
                return string.Format("{0}:{1}@{2}", Scheme, User, Host);
            encoding = encoding ?? _encoding;

            StringBuilder ret = new StringBuilder();
            if (!Relative)
            {
                ret.Append(Scheme + "://");
                if (User.Length > 0)
                {
                    ret.Append(User);
                    if (Password.Length > 0)
                        ret.Append(":" + Password);
                    ret.Append("@");
                }
                ret.Append(Host);
                if (!IsDefaultPort)
                    ret.Append(":" + Port);
            }
            ret.Append(Path);
            string query = GetQueryString(escaped, encoding);
            if (query.Length > 0)
                ret.Append("?" + query);
            if (Fragment.Length > 0)
                ret.Append("#" + Fragment);
            return ret.ToString();
        }

        /// <summary>
        /// 添加QueryString
        /// </summary>
        /// <param name="name">项的键</param>
        /// <param name="value">项的值</param>
        public void AppendQuery(string name, string value)
        {
            QueryString.Add(name, HttpUtility.UrlDecode(value, _encoding));
        }

        /// <summary>
        /// 添加QueryString
        /// </summary>
        /// <param name="array">QueryString集合</param>
        public void AppendQuery(NameValueCollection array)
        {
            for (int i = 0; i < array.Count; i++)
            {
                AppendQuery(array.GetKey(i), array.Get(i));
            }
        }
        #endregion

        #region Utils
        //重置解析器
        private void Reset()
        {
            Scheme = string.Empty;
            Port = 0;
            IsDefaultPort = true;
            Host = string.Empty;
            User = string.Empty;
            Password = string.Empty;
            Path = string.Empty;
            QueryString = new NameValueCollection();
            Fragment = string.Empty;
            Relative = false;
        }

        //解析URL
        private void DecodeURL(string value)
        {
            Match m = new Regex(_urlRegex).Match(value);
            if (m.Success)
            {
                if (m.Groups[1].Captures.Count == 1)
                    Scheme = m.Groups[1].Captures[0].Value;
                if (m.Groups[4].Captures.Count == 1)
                    User = m.Groups[4].Captures[0].Value;
                if (m.Groups[5].Captures.Count == 1)
                    Password = m.Groups[5].Captures[0].Value;
                if (m.Groups[6].Captures.Count == 1)
                    Host = m.Groups[6].Captures[0].Value;
                if (m.Groups[8].Captures.Count == 1)
                {
                    Port = Int32.Parse(m.Groups[8].Captures[0].Value);
                    IsDefaultPort = GetDefaultPort(Scheme) == Port;
                }
                else
                {
                    Port = GetDefaultPort(Scheme);
                    IsDefaultPort = true;
                }
                if (m.Groups[9].Captures.Count == 1)
                    Path = m.Groups[9].Captures[0].Value;
                if (m.Groups[11].Captures.Count == 1)
                    ParseQueryString(m.Groups[11].Captures[0].Value);
                if (m.Groups[13].Captures.Count == 1)
                    Fragment = m.Groups[13].Captures[0].Value;
            }
        }

        //解析mailto
        private void DecodeMailTo(string value)
        {
            Match m = new Regex(_mailtoRegex).Match(value);
            if (m.Success)
            {
                if (m.Groups[1].Captures.Count == 1)
                    Scheme = m.Groups[1].Captures[0].Value;
                if (m.Groups[2].Captures.Count == 1)
                    User = m.Groups[2].Captures[0].Value;
                if (m.Groups[3].Captures.Count == 1)
                    Host = m.Groups[3].Captures[0].Value;
            }
        }

        //解析QueryString
        private void ParseQueryString(string queryString)
        {
            QueryString.Clear();

            queryString = queryString.Trim('&', '?');
            string[] pairs = queryString.Split('&');
            for (int pairIdx = 0; pairIdx < pairs.Length; pairIdx++)
            {
                string pair = pairs[pairIdx];
                int keyPos = pair.IndexOf('=');
                if (keyPos > 0)
                {
                    string key = pair.Substring(0, keyPos);
                    string value = pair.Substring(keyPos + 1);
                    AppendQuery(key, value);
                }
                else
                {
                    AppendQuery(pair, string.Empty);
                }
            }
        }

        //获得QueryString
        private string GetQueryString(bool escaped, Encoding encoding)
        {
            StringBuilder ret = new StringBuilder();
            for (int i = 0; i < QueryString.Count; i++)
            {
                ret.Append((i == 0 ? "" : "&") + QueryString.Keys[i]);
                if (QueryString[i].Length > 0)
                {
                    if (escaped)
                        ret.Append("=" + HttpUtility.UrlEncode(QueryString[i], encoding));
                    else
                        ret.Append("=" + QueryString[i]);
                }
            }
            return ret.ToString();
        }

        //获得默认端口
        private int GetDefaultPort(string scheme)
        {
            int ret = 0;
            switch (scheme.ToUpper())
            {
                case "HTTP": ret = 80; break;
                case "FTP": ret = 21; break;
                case "HTTPS": ret = 443; break;
            }
            return ret;
        }
        #endregion

        #region 重写和接口
        /// <summary>
        /// 比较方法
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj == null) return false;
            if (obj.GetType() != this.GetType()) return false;
            return (GetEncodeUrl() == ((UrlParser)obj).GetEncodeUrl());
        }

        /// <summary>
        /// 获得哈希值
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return GetEncodeUrl().GetHashCode();
        }

        /// <summary>
        /// 输出URL全路径（已编码）
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return GetEncodeUrl();
        }

        /// <summary>
        /// 比较方法
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {
            if (obj == this) return 0;
            if (!(obj is UrlParser)) return -1;
            return ((UrlParser)obj).GetEncodeUrl().CompareTo(GetEncodeUrl());
        }

        /// <summary>
        /// 克隆
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            UrlParser newClone = (UrlParser)this.MemberwiseClone();
            newClone.QueryString = new NameValueCollection(QueryString);
            return newClone;
        }
        #endregion
    }
}
